using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using ERP.Common.Exceptions;
using ERP.Common.Extensions;
using ERP.EFCore;
using ERP.IDAL;
using ERP.Model;
using Microsoft.EntityFrameworkCore;

namespace ERP.DAL
{
    /// <summary>
    /// 仓储基类
    /// </summary>
    /// <typeparam name="TEntity">实体</typeparam>
    /// <typeparam name="TPrimaryKey">主键类型</typeparam>
    public abstract class RepositoryBase<TEntity, TPrimaryKey> : IRepositoryBase<TEntity, TPrimaryKey>
        where TEntity : BaseEntity<TPrimaryKey>
    {
        //定义数据访问上下文对象
        protected readonly DataContext DbContext;
        private readonly DbSet<TEntity> _dbSet;

        /// <summary>
        /// 通过构造函数注入得到数据上下文对象实例
        /// </summary>
        /// <param name="dbContext">上下文实例</param>
        protected RepositoryBase(DataContext dbContext)
        {
            DbContext = dbContext;
            _dbSet = DbContext.Set<TEntity>();
        }

        public IQueryable<TEntity> GetAll()
        {
            return GetAllIncluding();
        }

        public IQueryable<TEntity> GetAllIncluding(params Expression<Func<TEntity, object>>[] propertySelectors)
        {
            var query = _dbSet.AsQueryable();
            return propertySelectors.IsNullOrEmpty()
                ? query : propertySelectors.Aggregate(query, (current, propertySelector) => current.Include(propertySelector));
        }

        public async Task<List<TEntity>> GetAllListAsync()
        {
            return await GetAll().ToListAsync();
        }

        public async Task<List<TEntity>> GetAllListAsync(Expression<Func<TEntity, bool>> predicate)
        {
            return await GetAll().Where(predicate).ToListAsync();
        }

        public TEntity FirstOrDefault(TPrimaryKey id)
        {
            return GetAll().FirstOrDefault(CreateEqualityExpressionForId(id));
        }

        public TEntity FirstOrDefault(Expression<Func<TEntity, bool>> predicate)
        {
            return GetAll().FirstOrDefault(predicate);
        }

        public Task<TEntity> FirstOrDefaultAsync(TPrimaryKey id)
        {
            return Task.FromResult(FirstOrDefault(id));
        }

        public Task<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate)
        {
            return Task.FromResult(FirstOrDefault(predicate));
        }

        public TEntity Get(TPrimaryKey id)
        {
            var entity = FirstOrDefault(id);
            if (entity == null)
            {
                throw new EntityNotFoundExc(typeof(TEntity), id);
            }
            return entity;
        }

        public async Task<TEntity> GetAsync(TPrimaryKey id)
        {
            var entity = await FirstOrDefaultAsync(id);
            if (entity == null)
            {
                throw new EntityNotFoundExc(typeof(TEntity), id);
            }
            return entity;
        }

        public TEntity Insert(TEntity entity)
        {
            entity.AddedTime = DateTime.Now;
            entity.ModifiedTime = DateTime.Now;
            _dbSet.Add(entity);
            Save();
            return entity;
        }

        public Task<TEntity> InsertAsync(TEntity entity)
        {
            return Task.FromResult(Insert(entity));
        }

        public TPrimaryKey InsertAndGetId(TEntity entity)
        {
            return Insert(entity).Id;
        }

        public async Task<TPrimaryKey> InsertAndGetIdAsync(TEntity entity)
        {
            entity = await InsertAsync(entity);
            return entity.Id;
        }

        public TEntity Update(TEntity entity, Action<TEntity> updateAction = null)
        {
            var obj = Get(entity.Id);
            if (updateAction == null)
            {
                EntityToEntity(entity, obj);
            }
            else
            {
                updateAction(obj);
            }
            obj.ModifiedTime = DateTime.Now;
            Save();
            return obj;
        }

        public Task<TEntity> UpdateAsync(TEntity entity, Action<TEntity> updateAction = null)
        {
            return Task.FromResult(Update(entity, updateAction));
        }

        public TEntity InsertOrUpdate(TEntity entity)
        {
            return FirstOrDefault(entity.Id) == null ? Insert(entity) : Update(entity);
        }

        public async Task<TEntity> InsertOrUpdateAsync(TEntity entity)
        {
            return FirstOrDefaultAsync(entity.Id) == null ? await InsertAsync(entity) : await UpdateAsync(entity);
        }

        public void Delete(TEntity entity)
        {
            _dbSet.Remove(entity);
            Save();
        }

        public void Delete(TPrimaryKey id)
        {
            _dbSet.Remove(FirstOrDefault(id));
            Save();
        }

        public void Delete(Expression<Func<TEntity, bool>> predicate)
        {
            GetAll().Where(predicate).ToList().ForEach(Delete);
        }

        public Task DeleteAsync(TEntity entity)
        {
            Delete(entity);
            return Task.FromResult(0);
        }

        public Task DeleteAsync(TPrimaryKey id)
        {
            Delete(id);
            return Task.FromResult(0);
        }

        public Task DeleteAsync(Expression<Func<TEntity, bool>> predicate)
        {
            Delete(predicate);
            return Task.FromResult(0);
        }

        public IQueryable<TEntity> LoadPageList<TSKey>(int startPage, int pageSize, out int rowCount, Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, TSKey>> order, bool isAcs = true)
        {
            var result = from p in _dbSet
                         select p;
            rowCount = result.Where(predicate).Count();
            if (isAcs)
            {
                var temp = result.Where(predicate)
                    .OrderBy(order)
                    .Skip((startPage - 1) * pageSize)
                    .Take(pageSize);
                return temp;
            }
            else
            {
                var temp = result.Where(predicate)
                    .OrderByDescending(order)
                    .Skip((startPage - 1) * pageSize)
                    .Take(pageSize);
                return temp;
            }
        }

        public void Save()
        {
            DbContext.SaveChanges();
        }

        private static void EntityToEntity<T>(T pTargetObjSrc, T pTargetObjDest)
        {
            foreach (var mItem in typeof(T).GetProperties())
            {
                mItem.SetValue(pTargetObjDest, mItem.GetValue(pTargetObjSrc, new object[] { }), null);
            }
        }
        private static Expression<Func<TEntity, bool>> CreateEqualityExpressionForId(TPrimaryKey id)
        {
            var lambdaParam = Expression.Parameter(typeof(TEntity));
            var lambdaBody = Expression.Equal(Expression.PropertyOrField(lambdaParam, "Id"), Expression.Constant(id, typeof(TPrimaryKey)));
            return Expression.Lambda<Func<TEntity, bool>>(lambdaBody, lambdaParam);
        }
    }

    public abstract class RepositoryBase<TEntity> : RepositoryBase<TEntity, Guid> where TEntity : BaseEntity
    {
        protected RepositoryBase(DataContext dbContext) : base(dbContext)
        {

        }
    }
}